/* 算法实现模板 */
import AlgorithmBase from "./AlgorithmBase";
import DefaultValue from './input/TreePruning.input.json'
import Vector, { toVector } from "../../common/Vector";
import { drawKDTree } from "../../visual/Tree2D";
import { showMathStringInTable } from "../../visual/Table2D";
import { drawDecisionTree } from "../../visual/Tree2D";
import { createKDNode, NodeRange, calcNodeRange, KDNode } from "../../common/KDTreeFuncs";
class LSRegressionTree extends AlgorithmBase{
  constructor(){
    super('LSRegressionTree');
  }


  init(){
    this.print();
    var dataSet = DefaultValue.TrainDS.dataSet
    var label = DefaultValue.TrainDS.label
    var labeln = []
    var featuremap ={}
    var datat = this.getdatat(dataSet)
    this.drawtree = []
    this.step=1
    for(let a= 0;a<datat.length;a++){
      featuremap[label[a]] = this.getnoreapeatfeature(datat,a)
    }
    for (let index = 0; index < label.length; index++) {
      labeln.push(label[index]) 
    }
    console.log(featuremap);
    this.featuremap =featuremap
    datat = this.dealwithdata(datat)
   
   var nodetree= this.createNode(datat,labeln,null,null)
   console.log(nodetree);
   this.tree = this.createmap(nodetree);
   console.log(this.tree);
    // var lossdata = this.findBestSplit(datat,labeln);
    // console.log(lossdata); 
    // var lrdata = this.splitdatat(datat,lossdata);
    // // name,splitvalue,cleft,cright,lchildren,rchildren,ldatat,rdatat
    // var nd = new node(lossdata.name,lossdata.splitvalue,lossdata.cleft,lossdata.cright,null,null,lrdata.ldata,lrdata.rdata,lossdata.lossvalue)
    // var llossdata = this.findBestSplit(lrdata.ldata,lrdata.lastlabel) 
    // var rlossdata = this.findBestSplit(lrdata.rdata,lrdata.lastlabel) 
    // console.log(llossdata);
    // console.log(rlossdata);
    console.log(this.drawtree);
    if(this.domModel) this.domModel.innerHTML = '';
    drawDecisionTree(this.domModel, this.tree);
  }

  //建树
  createNode(datat,label,nd){
    if(nd==null){
      var lossdata = this.findBestSplit(datat,label);
      var lrdata = this.splitdatat(datat,lossdata)
      var nd = new node(lossdata.name,lossdata.splitvalue,lossdata.cleft,lossdata.cright,null,null,null,null,lossdata.lossvalue)
    }else{
      console.log(2);
      var lossdata = this.findBestSplit(datat,label);
      var lrdata = this.splitdatat(datat,lossdata)
    }
    if(lossdata.lossvalue>0){
      console.log('>0');
      nd.lchildren = new node()
      nd.rchildren = new node()
      nd.lchildren = this.createNode(lrdata.ldata,lrdata.lastlabel,nd.lchildren)
      nd.rchildren = this.createNode(lrdata.rdata,lrdata.lastlabel,nd.rchildren)
      return nd
    }else{
      nd = new node(lossdata.name,lossdata.splitvalue,lossdata.cleft,lossdata.cright,null,null,null,null,lossdata.lossvalue)
      return nd
    }
  }

  //创建map
  createmap(node){
    var map = {}
    map['node'] = node.name
    if(node.lchildren!=null){
      map[node.splitvalue] = this.createmap(node.lchildren)
      var otherkey = []
      //78-84行把剩余的特征取值放入map的key中
      for(let a = 0 ;a<this.featuremap[node.name].length;a++){
        if(this.featuremap[node.name][a]!=node.splitvalue){
          otherkey.push(this.featuremap[node.name][a])
        }
      }
      map[otherkey] = this.createmap(node.rchildren)
      return map
    }else{
      var otherkey = []
      //90-95行把剩余的特征取值放入map的key中
      for(let a = 0 ;a<this.featuremap[node.name].length;a++){
        if(this.featuremap[node.name][a]!=node.splitvalue){
          otherkey.push(this.featuremap[node.name][a])
        }
      }
      map[node.splitvalue] = {node: node.cleft}
      map[otherkey] = {node: node.cright}
      return map
    }
  }
  

  //将所有的datat的结果同意拒绝变成0,1
  dealwithdata(datat){
    for(let i =0 ; i<datat[datat.length-1].length;i++){
      if(datat[datat.length-1][i]=='同意'){
        datat[datat.length-1][i]=1
      }else{
        datat[datat.length-1][i]=0
      }
    }
    return datat
  }

  //给下次循环的datat,lossdata中的ij存的都是当前datat的ij，只不过i一样
  splitdatat(datat,lossdata){
    var targeti = lossdata.i
    var data = this.getdatat(datat)
    var ldatat= []
    var rdatat= []
    var label= []
    // var ldatat= Array.from(Array(lossdata.lnum), () => new Array(datat.length));
    // var rdatat= Array.from(Array(lossdata.rnum), () => new Array(datat.length));
    for(let j=0;j<datat[targeti].length;j++){
      if(datat[targeti][j]==lossdata.splitvalue){
        ldatat.push(data[j])
      }else{
        rdatat.push(data[j])
      }
    }
    ldatat = this.getdatat(ldatat)
    rdatat = this.getdatat(rdatat)
    ldatat.splice(lossdata.i,1)
    rdatat.splice(lossdata.i,1)
    label = lossdata.lastlabel
    console.log(ldatat);
    console.log(rdatat);
    return new lrdata(ldatat,rdatat,label)
  }

  //寻找切分点
  findBestSplit(datat,label){
    var losslist = []
    for(let i = 0; i<datat.length-1 ;i++){//确定特征 -1去掉结果
      var everyfeaturearr = this.getnoreapeatfeature(datat,i)
      for(let e =0 ; e<everyfeaturearr.length;e++){//确定特征值
        var lnum = 0
        var rnum = 0
        var cleft = 0
        var cright = 0
        var llist = []
        var rlist = []
        for(var j = 0; j<datat[i].length ;j++){//遍历该特征值数据分左右支
          if(datat[i][j]==everyfeaturearr[e]){
            llist.push(datat[datat.length-1][j])
            lnum++
            cleft+=datat[datat.length-1][j]
          }else{
            rnum++
            cright+=datat[datat.length-1][j]
            rlist.push(datat[datat.length-1][j])
          }
        }
        j--
        cleft = cleft / lnum
        cright = cright / rnum
        var name = label[i]
        var ld = new lossdata(this.lossFunction(llist,rlist,cleft,cright),i,j,cleft,cright,everyfeaturearr[e],lnum,rnum,name,label)
        var dt =new drawlist(ld.name,ld.splitvalue,ld.lossvalue,ld.cleft,ld.cright,this.step)
        
        losslist.push(ld)
        this.drawtree.push(dt)
      }
    }
    //此时拿到了所有的切分点的cl，cr，i，j数据冒泡取出最小值
    var ldtemp= losslist[0]
    for(let i = 0;i<losslist.length-1;i++){
      for(let j =i;j<losslist.length;j++){
        if(losslist[j].lossvalue<losslist[i].lossvalue){
          ldtemp = losslist[j]
          losslist[j] = losslist[i]
          losslist[i] = ldtemp
        }
      }
    }
    //0是最小值，我为啥要用冒泡嘞？
    var newlabel = []
    for(let la =0;la<label.length;la++){
      if(la!=losslist[0].i)
        newlabel.push(label[la])
    }
    losslist[0].lastlabel = newlabel
    this.step++
    return losslist[0]
  }

  

  //create map
  createmapold(datat,label,i,alreadyused){
    console.log(datat);
    if(i<datat.length-2){
      i++
      var nodedata =this.findBestVariable(datat,label,alreadyused)
      alreadyused.push(nodedata.i)
      console.log(nodedata.name);
      console.log(alreadyused);
      console.log(nodedata.datat);
      return this.createmap(this.dealdata(nodedata),label,i,alreadyused)
    }
    var nodedata =this.findBestVariable(datat,label,alreadyused)
    console.log(nodedata.name);
    return this.dealdata(nodedata)
  }
  
  //处理data  旧版不用
  dealdata(nodedata){
    console.log(('dealdata运行'));
    var datat = nodedata.datat
    console.log(nodedata.cleft,nodedata.cright,nodedata.j);
    //将已经确定cleft cright的特征的左右两支的值变成固定值
    for (let i = 0; i < datat[0].length; i++) {
      if(datat[nodedata.i][i]>nodedata.point){
        datat[datat.length-1][i] = nodedata.cright*1
      }else{
        datat[datat.length-1][i] = nodedata.cleft*1
      }
    }
    return datat
  }

  //寻找最优切分变量及切分点的调用方法用于判断是否有左右分支不用
  findBestVariablefront(node,label,alreadyused,i,datat){
    if(node== null){
      var node = this.findBestVariable(datat,label,alreadyused)
    }
    if(i<label.length-2){
      alreadyused.push(node.i)
      i++
      node.lchildren =  this.findBestVariablefront(node,label,alreadyused,i)
      node.rchildren  =  this.findBestVariablefront(node,label,alreadyused,i)
      return node
    }else{
      var lnode =this.findBestVariable(this.getdatat(node.ldata),label,node.alreadyused)
      var rnode =this.findBestVariable(this.getdatat(node.rdata),label,node.alreadyused)      
      node.lchildren = lnode 
      node.rchildren = rnode 
      return node
    }
  }

  //寻找最优切分变量及切分点不用
  findBestVariable(datat,label,alreadyused){
    var leftsum = 0
    var rightsum = 0
    var lossdata =[]
    var data = this.getdatat(datat)
    for(var i = 0 ;i<datat.length-1;i++){//去掉结果
      if(alreadyused.indexOf(i)!=-1)
        continue
      var hash = this.getnoreapeatfeature(datat,[i])
      console.log('特征列表为'+hash);
      for(var j= 0 ;j<hash.length ; j++){ //切分点三个值两个切分点
        console.log("第"+i,j+"次循环");
        var cleft = 0  //左切分点
        var cright = 0
        var losssum=0
        var clnum = 0   //在左切分点区域的元素个数
        var crnum = 0
        var cllist = []  //在左切分点区域的元素列表
        var crlist = []
        var ldata = []
        var rdata = []
        // for(var c=0;c<hash.length-1 ;c++){
        //   //在选定了hash【j】为切分点后确定左右c的值为平均数
        //   if(hash[c]>hash[j])
        //   {
        //     cright +=hash[c]
        //   }else{
        //     cleft  +=hash[c]
        //   }
        // }



        //选取切分点  左右c的值为平均数
        for(var k = 0;k<datat[i].length ; k++){
          if(datat[i][k]>hash[j]){
            cright += datat[datat.length-1][k]*1
            crnum ++
            crlist.push(k)
            rdata.push(data[k][i])
          }else{
            cleft += datat[datat.length-1][k]*1
            clnum ++
            cllist.push(k)
            ldata.push(data[k][i])
          }
        }
        if(clnum==0||crnum==0) //分界点在边上进入下次循环
          {
            console.log("第"+i,j+"次循环是边界被跳过了");
            continue
          }
        cright = cright/crnum
        cleft = cleft/clnum
        
        console.log("c的取值为"+cright,crnum,cleft,clnum);
        for(var kk = 0;kk<cllist.length ; kk++){//遍历取值计算左右分区的输出值
            // if(datat[i][k]!=hash[j]){//与所选特征值不同，加到右边区域
            console.log('左值',cllist[kk]);
            // }
            losssum += (this.lossFunction(datat[datat.length-1][cllist[kk]],cleft))*1
        }
        for(var kk = 0;kk<crlist.length ; kk++){//遍历取值计算左右分区的输出值
          // if(datat[i][k]!=hash[j]){//与所选特征值不同，加到右边区域

          // }
            losssum += (this.lossFunction(datat[datat.length-1][crlist[kk]],cright))*1
        }
            console.log("计算出的损失函数值为"+losssum,j,i);
            lossdata.push([losssum,j,i,cleft,cright,rdata,ldata])
      }
     
    }
    var min =lossdata[0][0]
    var minj =lossdata[0][1]
    var mini =lossdata[0][2]
    rdata = lossdata
    for(var ll =0;ll<lossdata.length;ll++)
      console.log("lossdata的值为losssum,j,i,cleft,cright"+lossdata[ll],ll);
    //lossdata的值是每一次循环的loss值即i特征，j取值
    for(var z = 0;z<lossdata.length;z++){
      if(lossdata[z][0]<min)
        min = lossdata[z][0]
        minj =lossdata[z][1]
        mini =lossdata[z][2]
        cleft = lossdata[z][3]
        cright = lossdata[z][4]
        rdata = lossdata[z][5]
        rdata = lossdata[z][6]
    }

    console.log(cright);
    //min为最小损失函数  minj为最小特征值  mini为最小特征
    console.log(min,minj,mini,cleft,cright);
    return new nodedata(cleft,cright,mini,minj,label[mini],datat[mini][minj],datat,ldata,rdata)
  }

  //损失函数
  lossFunction(llist,rlist,cleft,cright){
    var lsum =0
    var rsum =0
    for(let a = 0 ;a<llist.length;a++){
      lsum += Math.pow((llist[a]-cleft),2)
    }
    for(let a = 0 ;a<rlist.length;a++){
      rsum += Math.pow((rlist[a]-cright),2)
    }
    return lsum+rsum
    

  }  

  //获取不重复特征  找地index行的不重复特征
  getnoreapeatfeature(datat,index){
    var hash =[];
    for (var i = 0; i < datat[index].length; i++) {
      if(hash.indexOf(datat[index][i])==-1){
      hash.push(datat[index][i]);
      } 
    }
    return hash
  }
  
  //获取转置   带结果
  getdatat(dataset){
    var datat= Array.from(Array(dataset[0].length), () => new Array(dataset.length));
    var linemax = dataset.length //5
    var rowmax  = dataset[0].length // 3
    for(let row = 0;row<rowmax;row++){//3
      for(let line = 0;line<linemax;line++){//5
        datat[row][line] = dataset[line][row]
      }
    }
    return datat;

  }

  //显示Train



  displayTrain(){
    var dataSetr = DefaultValue.TrainDS.dataSet
    var labelr = DefaultValue.TrainDS.label
    var dataSet =  Array.from(Array(dataSetr.length), () => new Array(dataSetr[0].length));
    var label =  Array(labelr.length)
    for(let a=0;a<dataSet.length;a++){
      for(let b =0 ;b<dataSet[a].length;b++){
        dataSet[a][b] = dataSetr[a][b]
      }
    }
    for(let a= 0;a<label.length;a++)
      label[a] = labelr[a] 
    var trainLog = []
    var label_=[]
    for(var i=0;i<label.length;i++){
      label_.push("\\textbf{"+label[i]+"}")
    }

    trainLog.push(label_)

    for(var j=0;j<dataSet.length;j++){
      var data_=[]
      for(var k=0;k<dataSet[j].length;k++){
        data_.push("\\textbf{"+dataSet[j][k]+"}")
      }
      trainLog.push(data_)
    }
    if(this.domTrain) this.domTrain.innerHTML = '';
    showMathStringInTable(this.domTrain, trainLog);
   
  }

  loadModel(json){

if(json.TrainDS){
  let tmp = JSON.parse(json.TrainDS);
  this.trainDS.T = toVector(tmp.T, true);
  console.log(this.trainDS);
}

if(json.Model){
  console.log(1);
  let tmp = JSON.parse(json.Model);
  let node = new KDNode();
  node.copyFromJSON(tmp.node, true);
  this.model.node = node;
}

if(json.TestDS){
  this.testDS.T = toVector(json.TestDS.T, true);
}
}


  displayModel(){

let node = this.model.node;

if(this.figure){
  let range = new NodeRange([[this.figure.xMin, this.figure.xMax + 1],[this.figure.yMin,this.figure.yMax + 1]]);

  calcNodeRange(node, range);
  // 绘制分割线
  this.drawSplitLine(node);
  this.figure.update();
}

if(this.domModel) this.domModel.innerHTML = '';
drawKDTree(this.domModel, node);

}

  test(){
    const T = this.testDS.T;
    const tree = this.tree
    let result = [];
    let testLog = [];
    testLog.push([
      String.raw`\textbf{步骤}`, 
      String.raw`\textbf{特征}`, 
      String.raw`\textbf{切分点}`, 
      String.raw`\textbf{损失函数}`, 
      String.raw`\textbf{左值}`, 
      String.raw`\textbf{右值}`]);
    for(let a = 0 ;a<this.drawtree.length;a++){
      testLog.push([`${this.drawtree[a].step}`,`${this.drawtree[a].name}`,`${this.drawtree[a].splitvalue}`,`${this.drawtree[a].lossvalue.toFixed(4)}`,`${this.drawtree[a].cleft.toFixed(2)}`,`${this.drawtree[a].cright.toFixed(2)}`])
      if(a!=(this.drawtree.length-1)&&this.drawtree[a].step!=this.drawtree[a+1].step){
        console.log(a);
        testLog.push(['----','----','----','----','----','----']);
      }
    }

    if(this.domTable) this.domTable.innerHTML = '';
    showMathStringInTable(this.domTable, testLog);
    this.testDS.R = result;
    console.log(this.drawtree);
    if(this.figure) this.figure.clearAll();
  }
  }

//find最佳特征值和切分点的返回类型 不用
//用于保存cleft cright name  切分特征 i  切分特征值（特征点）j point 特征点的值
class nodedata{
  constructor(cleft,cright,i,j,name,point,datat,ldata,rdata,lchildren,rchildren){
    this.cleft = cleft
    this.cright = cright
    this.i = i
    this.j = j
    this.name = name
    this.point = point
    this.datat = datat
    this.ldata = ldata
    this.rdata = rdata
    this.lchildren = lchildren
    this.rchildren = rchildren
  }
}



//保存分割点数据  分割datat用
//损失函数值，第i个特征值，j个变量 ，左支输出值，右支输出值
//分割点，左支个数，右支个数
class lossdata{
  constructor(lossvalue,i,j,cleft,cright,splitvalue,lnum,rnum,name,lastlabel){
    this.lossvalue = lossvalue
    this.i = i
    this.j = j
    this.cleft = cleft
    this.cright = cright
    this.splitvalue = splitvalue
    this.lnum = lnum
    this.rnum = rnum
    this.name = name
    this.lastlabel =lastlabel
  }
    
}


//保存结点关系创建树用
class node{
  constructor(name,splitvalue,cleft,cright,lchildren,rchildren,ldatat,rdatat,lossvalue,splitothervalue){
    this.name = name
    this.splitvalue = splitvalue
    this.lchildren = lchildren
    this.rchildren = rchildren
    this.ldatat = ldatat
    this.rdatat = rdatat
    this.cleft = cleft
    this.cright = cright
    this.lossvalue = lossvalue
    this.splitothervalue = splitothervalue
  }
}

// 保存左右支关系
class lrdata{
  constructor(ldata,rdata,lastlabel){
    this.ldata = ldata
    this.rdata = rdata
    this.lastlabel = lastlabel
  }
}

//保存步骤中绘制表所需要显示的数据
class drawlist{
  constructor(name,splitvalue,lossvalue,cleft,cright,step){
    this.name=name
    this.splitvalue=splitvalue
    this.lossvalue=lossvalue
    this.cleft=cleft
    this.cright=cright
    this.step=step

  }

}


export default LSRegressionTree;